#include <types.h>
#include <stdio.h>
#include <string.h>
#include <slab.h>
#include <list.h>
#include <fs.h>
#include <vfs.h>
#include <dev.h>
#include <sfs.h>
#include <inode.h>
#include <iobuf.h>
#include <bitmap.h>
#include <error.h>
#include <assert.h>
#include <kio.h>

/*
 * Sync routine. This is what gets invoked if you do FS_SYNC on the
 * sfs filesystem structure.
 */

static int sfs_sync(struct fs *fs)
{
	struct sfs_fs *sfs = fsop_info(fs, sfs);
	lock_sfs_fs(sfs);
/*
 * Get the sfs_fs from the generic abstract fs.
 *
 * Note that the abstract struct fs, which is all the VFS
 * layer knows about, is actually a member of struct sfs_fs.
 * The pointer in the struct fs points back to the top of the
 * struct sfs_fs - essentially the same object. This can be a
 * little confusing at first.
 *
 * The following diagram may help:
 *
 *     struct sfs_fs        <-----------------\
        *           :                                |
        *           :   sfs_absfs (struct fs)        |   <------\
        *           :      :                         |          |
        *           :      :  various members        |          |
        *           :      :                         |          |
        *           :      :  fs_info(__sfs_info) ---/          |
        *           :      :                             ...|...
        *           :                                   .  VFS  .
        *           :                                   . layer . 
        *           :   other members                    .......
        *           :                                    
        *           :
 *
 * This construct is repeated with inodes and devices and other
 * similar things all over the place in ucore, so taking the
 * time to straighten it out in your mind is worthwhile.
 */
	{
		list_entry_t *list = &(sfs->inode_list), *le = list;
		while ((le = list_next(le)) != list) {
			struct sfs_inode *sin = le2sin(le, inode_link);
			vop_fsync(info2node(sin, sfs_inode));
		}
	}
	unlock_sfs_fs(sfs);

	int ret;
	if (sfs->super_dirty) {
		sfs->super_dirty = 0;
		/* If the superblock needs to be written, write it. */
		if ((ret = sfs_sync_super(sfs)) != 0) {
			sfs->super_dirty = 1;
			return ret;
		}
		/* If the free block map needs to be written, write it. */
		if ((ret = sfs_sync_freemap(sfs)) != 0) {
			sfs->super_dirty = 1;
			return ret;
		}
	}
	return 0;
}

/*
 * Get inode for the root of the filesystem.
 * The root inode is always found in block 1 (SFS_ROOT_LOCATION).
 */

static struct inode *sfs_get_root(struct fs *fs)
{
	struct inode *node;
	int ret;
	if ((ret =
	     sfs_load_inode(fsop_info(fs, sfs), &node, SFS_BLKN_ROOT)) != 0) {
		panic("load sfs root failed: %e", ret);
	}
	return node;
}

/*
 * Unmount code.
 *
 * VFS calls FS_SYNC on the filesystem prior to unmounting it.
 */

static int sfs_cleanup(struct fs *fs)
{
	struct sfs_fs *sfs = fsop_info(fs, sfs);
	if (!list_empty(&(sfs->inode_list))) {
		return -E_BUSY;
	}
	assert(!sfs->super_dirty);
	bitmap_destroy(sfs->freemap);
	kfree(sfs->sfs_buffer);
	kfree(sfs->hash_list);
	kfree(sfs);
	return 0;
}

static int sfs_unmount(struct fs *fs)
{
	struct sfs_fs *sfs = fsop_info(fs, sfs);
	uint32_t blocks = sfs->super.blocks, unused_blocks =
	    sfs->super.unused_blocks;
	kprintf("sfs: unmount: '%s' (%d/%d/%d)\n", sfs->super.info,
		blocks - unused_blocks, unused_blocks, blocks);
	int i, ret;
	for (i = 0; i < 32; i++) {
		if ((ret = fsop_sync(fs)) == 0) {
			break;
		}
	}
	if (ret != 0) {
		warn("sfs: sync error: '%s': %e.\n", sfs->super.info, ret);
	}
	return ret;
}

static int sfs_init_read(struct device *dev, uint32_t blkno, void *blk_buffer)
{
	struct iobuf __iob, *iob =
	    iobuf_init(&__iob, blk_buffer, SFS_BLKSIZE, blkno * SFS_BLKSIZE);
	return dop_io(dev, iob, 0);
}

static int
sfs_init_freemap(struct device *dev, struct bitmap *freemap, uint32_t blkno,
		 uint32_t nblks, void *blk_buffer)
{
	size_t len;
	void *data = bitmap_getdata(freemap, &len);
	assert(data != NULL && len == nblks * SFS_BLKSIZE);
	while (nblks != 0) {
		int ret;
		if ((ret = sfs_init_read(dev, blkno, data)) != 0) {
			return ret;
		}
		blkno++, nblks--, data += SFS_BLKSIZE;
	}
	return 0;
}

/*
 * SFS Mount.
 *
 * The way mount works is that you call vfs_mount and pass it a
 * filesystem-specific mount routine. This routine takes a device and
 * hands back a pointer to an abstract filesystem. You can also pass
 * a void pointer through.
 *
 * This organization makes cleanup on error easier. Hint: it may also
 * be easier to synchronize correctly; it is important not to get two
 * filesystem with the same name mounted at once, or two filesystems
 * mounted on the same device at once.
 */

static int sfs_do_mount(struct device *dev, struct fs **fs_store)
{

/*
 * Make sure SFS on-disk structures aren't messed up
 */
	static_assert(SFS_BLKSIZE >= sizeof(struct sfs_super));
	static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_inode));
	static_assert(SFS_BLKSIZE >= sizeof(struct sfs_disk_entry));

/*
 * We can't mount on devices with the wrong sector size.
 *
 * (Note: for all intents and purposes here, "sector" and
 * "block" are interchangeable terms. Technically a filesystem
 * block may be composed of several hardware sectors, but we
 * don't do that in sfs.)
 */
	if (dev->d_blocksize != SFS_BLKSIZE) {
		return -E_NA_DEV;
	}

	/* allocate fs structure */
	struct fs *fs;
	if ((fs = alloc_fs(sfs)) == NULL) {
		return -E_NO_MEM;
	}

	/* get sfs from fs.fs_info.__sfs_info */
	struct sfs_fs *sfs = fsop_info(fs, sfs);
	sfs->dev = dev;

	int ret = -E_NO_MEM;

	void *sfs_buffer;
	if ((sfs->sfs_buffer = sfs_buffer = kmalloc(SFS_BLKSIZE)) == NULL) {
		goto failed_cleanup_fs;
	}

	/* load and check sfs's superblock */
	if ((ret = sfs_init_read(dev, SFS_BLKN_SUPER, sfs_buffer)) != 0) {
		goto failed_cleanup_sfs_buffer;
	}

	ret = -E_INVAL;

	struct sfs_super *super = sfs_buffer;

	/* Make some simple sanity checks */
	if (super->magic != SFS_MAGIC) {
		kprintf
		    ("sfs: wrong magic in superblock. (%08x should be %08x).\n",
		     super->magic, SFS_MAGIC);
		goto failed_cleanup_sfs_buffer;
	}
	if (super->blocks > dev->d_blocks) {
		kprintf("sfs: fs has %u blocks, device has %u blocks.\n",
			super->blocks, dev->d_blocks);
		goto failed_cleanup_sfs_buffer;
	}
	super->info[SFS_MAX_INFO_LEN] = '\0';
	sfs->super = *super;

	ret = -E_NO_MEM;

	uint32_t i;

	/* alloc and initialize hash list */
	list_entry_t *hash_list;
	if ((sfs->hash_list = hash_list =
	     kmalloc(sizeof(list_entry_t) * SFS_HLIST_SIZE)) == NULL) {
		goto failed_cleanup_sfs_buffer;
	}
	for (i = 0; i < SFS_HLIST_SIZE; i++) {
		list_init(hash_list + i);
	}

	/* load and check freemap (free space bitmap in disk) */
	struct bitmap *freemap;
	uint32_t freemap_size_nbits = sfs_freemap_bits(super);
	if ((sfs->freemap = freemap =
	     bitmap_create(freemap_size_nbits)) == NULL) {
		goto failed_cleanup_hash_list;
	}
	uint32_t freemap_size_nblks = sfs_freemap_blocks(super);
	if ((ret =
	     sfs_init_freemap(dev, freemap, SFS_BLKN_FREEMAP,
			      freemap_size_nblks, sfs_buffer)) != 0) {
		goto failed_cleanup_freemap;
	}

	uint32_t blocks = sfs->super.blocks, unused_blocks = 0;
	for (i = 0; i < freemap_size_nbits; i++) {
		if (bitmap_test(freemap, i)) {
			unused_blocks++;
		}
	}
	assert(unused_blocks == sfs->super.unused_blocks);

	/* and other fields */
	sfs->super_dirty = 0;
	sem_init(&(sfs->fs_sem), 1);
	sem_init(&(sfs->io_sem), 1);
	sem_init(&(sfs->mutex_sem), 1);
	list_init(&(sfs->inode_list));
	kprintf("sfs: mount: '%s' (%d/%d/%d)\n", sfs->super.info,
		blocks - unused_blocks, unused_blocks, blocks);

	/* Set up abstract fs calls */
	fs->fs_sync = sfs_sync;
	fs->fs_get_root = sfs_get_root;
	fs->fs_unmount = sfs_unmount;
	fs->fs_cleanup = sfs_cleanup;

	*fs_store = fs;
	return 0;

failed_cleanup_freemap:
	bitmap_destroy(freemap);
failed_cleanup_hash_list:
	kfree(hash_list);
failed_cleanup_sfs_buffer:
	kfree(sfs_buffer);
failed_cleanup_fs:
	kfree(fs);
	return ret;
}

/*
 * Actual function called from high-level code to mount an sfs.
 */

int sfs_mount(const char *devname)
{
	return vfs_mount(devname, sfs_do_mount);
}
